package com.dcits.business;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.FileUtils;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.WebDriverWait;

import com.dcits.selenium.TestElement;
import com.dcits.selenium.TestStep;
import com.dcits.util.DcitsUtil;
import com.dcits.util.db.ExecOperater;
import com.dcits.util.db.TestDB;
import com.dcits.util.logger.Logger;

/**
 * 测试用例基础类<br>
 * 新的测试用例继承并实现execute()方法即可
 * @author xuwangcheng
 * @version 2018.3.22
 *
 */
public abstract class BaseTest {
	
	protected static final Logger logger = Logger.getLogger(BaseTest.class);
	protected WebDriver driver;
	
	public static final String PUBLIC_MODULE_PACKGE_PREFIX = "com.dcits.business.module";
	
	public static final Integer MAX_SECOND_TO_WAIT_ELEMENT = 10;
	
	public static final String RESULT_STATUS_ATTRIBUTE_NAME = "status";
	public static final String RESULT_MARK_ATTRIBUTE_NAME = "mark";
	public static final String RESULT_CASE_ATTRIBUTE_NAME = "caseName";
	public static final String RESULT_TEST_DATA_ATTRIBUTE_NAME = "testData";
	public static final String RESULT_USE_TIME_ATTRIBUTE_NAME = "useTime";
	public static final String RESULT_FINISH_TIME_ATTRIBUTE_NAME = "finishTime";
	public static final String RESULT_SCREENSHOT_ATTRIBUTE_NAME = "screenshot";
	public static final String RESULT_TEST_STEPS_ATTRIBUTE_NAME = "steps";
	
	
	public static final String SUCCESS_STATUS = "success";
	public static final String FAIL_STATUS = "fail";
	public static final String ERROR_STATUS = "error";

	private Map<String, Object> resultMap = new HashMap<String, Object>();
	private List<TestStep> steps = new ArrayList<TestStep>();
	private TestStep currentStep = null;
	
	public BaseTest() {
		// TODO Auto-generated constructor stub
		resultMap.put(RESULT_STATUS_ATTRIBUTE_NAME, SUCCESS_STATUS);
		resultMap.put(RESULT_MARK_ATTRIBUTE_NAME, "");
		resultMap.put(RESULT_CASE_ATTRIBUTE_NAME, getClass().getSimpleName());
		resultMap.put(RESULT_TEST_DATA_ATTRIBUTE_NAME, "");
		resultMap.put(RESULT_USE_TIME_ATTRIBUTE_NAME, "");
		resultMap.put(RESULT_SCREENSHOT_ATTRIBUTE_NAME, new ArrayList<String>());
		resultMap.put(RESULT_TEST_STEPS_ATTRIBUTE_NAME, steps);
	}
	
	/**
	 * 执行入口<br> 
	 * @return 测试结果<br>
	 * <strong>
	 * status(测试结果):success-通过，fail-不通过，error-异常中断<br>
	 * mark(测试备注):记录失败或者异常信息<br>
	 * caseName(用例名称):测试用例名称<br>
	 * testData(测试数据):当前使用的测试数据
	 * </strong>
	 * @throws Exception 
	 */
	public abstract void execute() throws Exception;
	
	
	/************************************selenum通用方法的封装******************************************************/
	/**
	 * 对当前页面进行截图操作:只能截图浏览器
	 * @param driver
	 * @return booelan 截图是否成功
	 */
	public boolean screenshot() {
		String pngName = String.valueOf(System.currentTimeMillis())+".png";
		String capturePath = DcitsUtil.SCREENSHOT_PATH + "/" + pngName;
		File screenShotFile = ((TakesScreenshot)this.driver).getScreenshotAs(OutputType.FILE); 
		try {
			FileUtils.copyFile(screenShotFile, new File(capturePath));
			this.addScreenShot(capturePath);
		} catch (Exception e) {
			// TODO: handle exception
			logger.error("截图出错!" + e.getMessage(), e);
			return false;
		}
		return true;		
	}
	
	
	/**
	 * X掉关闭或者取消掉对话框
	 */
	public void dialogDismiss(){
		Alert alert = driver.switchTo().alert();
		alert.dismiss();
	}
	
	/**
	 * 点击对话框的确认按钮
	 */
	public void dialogAccept(){
		Alert alert = driver.switchTo().alert();
		alert.accept();
	}
	
	/**
	 * 获取对话框中文本并点击确认
	 * @return
	 */
	public String getDialogText(){
		Alert alert = driver.switchTo().alert();
    	String a = alert.getText();
    	alert.accept();
    	return a;		
	}
	
	/**
	 * 发送字符串给prompt(文本对话框)
	 * @param keys
	 */
	public void sendKeyDialog(String keys){
		Alert alert = driver.switchTo().alert();
		alert.sendKeys(keys);
		alert.accept();
	}
	
	/**
	 * 根据下拉框的option的value属性来指定下拉选项
	 * @param value
	 */
	public void selectByValue(WebElement element, String value){
		Select select = new Select(element);
		select.selectByValue(value);
	}
	
	/**
	 * 根据下拉框的option的可见文本值来指定下拉选项
	 * @param element
	 * @param text
	 */
	public static void selectByOptionText(WebElement element,String text){
		Select select = new Select(element);
		select.selectByVisibleText(text);
	}
	
	/**
	 * 切换窗口,参数是窗口名称或者窗口的句柄handle
	 * @param nameOrHandle
	 */
	public void switchToWindow(String nameOrHandle){
		driver.switchTo().window(nameOrHandle);
	}
	
	/**
	 * 根据窗口索引来切换窗口
	 * @param windowIndex
	 */
	public void switchToWindow(int windowIndex) {
		List<String> handles = new ArrayList<String>(driver.getWindowHandles());
		driver.switchTo().window(handles.get(windowIndex));
	}
	
	/**
	 * 鼠标事件<br>
	 * 双击
	 * @param element
	 */
	public void mouseDoubleClick(WebElement element) {
		new Actions(driver).doubleClick(element).perform();
	}
	
	/**
	 * 鼠标事件<br>
	 * 悬停或者移动到指定元素上
	 * @param element
	 */
	public void mouseHover(WebElement element) {
		new Actions(driver).moveToElement(element).perform();
	}
	
	/**
	 * 鼠标事件<br>
	 * 元素拖拽
	 * @param element 需要拖拽的元素
	 * @param begin 起始位置
	 * @param end 终点位置
	 */
	public void mouseDragAndDrop(WebElement element, WebElement begin, WebElement end) {
		element.click();
		new Actions(driver).dragAndDrop(begin, end).perform();
	}
	/**
	 * 鼠标事件<br>
	 * 右击
	 * @param element
	 */
	public void mouseRightClick(WebElement element) {
		new Actions(driver).contextClick(element).perform();
	}
	
	/**
	 * 发送字符串到文本框或者其他<br>
	 * 如果普通的element.sendKeys无法使用，请尝试使用此方法
	 * @param element
	 * @param keys
	 */
	public void mouseSendKeys(WebElement element, String keys) {
		Actions action = new Actions(driver);
		action.moveToElement(element);
		action.click();
		action.sendKeys(keys);
		action.perform();
	}
	
	/**
	 * 判断节点是否存在并做相应操作
	 * @param element 不确定存在的元素
	 * @param how 操作类型,请与WebElement中方法名一致否则可能会报错
	 * @param value 需要的数据，对于某些方法来说
	 * @throws SecurityException 
	 * @throws NoSuchMethodException 
	 * @throws InvocationTargetException 
	 * @throws IllegalArgumentException 
	 * @throws IllegalAccessException 
	 */
	public void operationFuzzyElement(By by, String how, Object... value) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
		WebElement element = getFuzzyElement(by);
		if (element == null) {
			return;
		}
		element.getClass().getMethod(how).invoke(element, value);
	}
	
	
	/**
	 * 传入By数组,从而实现嵌套查询元素并执行
	 * @param bys  嵌套查询条件
	 * @param how 操作类型
	 * @param value 操作所需参数
	 * @throws IllegalArgumentException
	 * @throws SecurityException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws NoSuchMethodException
	 */
	public void operationFuzzyElement(By[] bys, String how, Object... value) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
		WebElement element = getFuzzyElement(bys);
		if (element == null) return;
		element.getClass().getMethod(how).invoke(element, value);
	}
	
	/**
	 * 获取不一定存在的元素
	 * @param bys 嵌套查询条件
	 * @return
	 */
	public WebElement getFuzzyElement(By[] bys) {
		if (bys.length < 1) {
			return null;
		}
		SearchContext sc = driver;
		try {
			for (int i = 0;i < bys.length - 1;i++) {
				sc = sc.findElement(bys[i]);
			}
			return sc.findElement(bys[bys.length - 1]);
		} catch (Exception e) {
			// TODO: handle exception
			logger.info("获取元素失败:" + e.getMessage());			
		}
		return null;
	}
	
	/**
	 * 获取不一定存在的元素
	 * @param by 查询条件By
	 * @return
	 */
	public WebElement getFuzzyElement(By by) {
		try {
			return driver.findElement(by);
		} catch (Exception e) {
			// TODO: handle exception
			logger.info("获取元素失败:" + e.getMessage());	
		}
		return null;
	}
	

	/**
	 * 根据配置信息获取By实例
	 * @param objectKey
	 * @return
	 * @throws Exception
	 */
	public By By(String elementKey) throws Exception {
		return TestElement.getBy(elementKey);
	}
	
	/**
	 * 根据配置信息获取TestElement对象
	 * @param objectKey
	 * @return
	 * @throws Exception
	 */
	public TestElement getElement(String elementKey) throws Exception {
		return TestElement.getElement(elementKey, driver);
	}
	
	/**
	 * 获取WebElement
	 * @param elementKey
	 * @return
	 * @throws Exception
	 */
	public WebElement getWebElement(String elementKey) throws Exception {
		return getElement(elementKey).getWebElement();
	}
	
	
	/**
	 * 等待获取对象元素<br>使用WebDriverWait方式
	 * @param objectKey
	 * @param seconds
	 * @return
	 */
	public WebElement getElementForWait(final String objectKey, Integer seconds) {
		WebDriverWait wait = new WebDriverWait(driver, seconds);
		return wait.until(new ExpectedCondition<WebElement>() {
			@Override
			public WebElement apply(WebDriver d) {
				try {
					return getElement(objectKey).getWebElement();
				} catch (Exception e) {
					logger.error("等待获取元素失败:" + objectKey, e);
				}
				return null;
			}
		});
	}

	/**
	 * 等待获取对象元素,默认10s<br><br>使用WebDriverWait方式
	 * @param objectKey
	 * @return
	 */
	public WebElement getElementForWait(final String objectKey) {
		return getElementForWait(objectKey, MAX_SECOND_TO_WAIT_ELEMENT);
	}
	
	/**
	 * 判断元素是否存在
	 * @param by
	 * @return
	 */
	private WebElement isElementPresent(By by) {
		try {
			return driver.findElement(by);
		} catch (NoSuchElementException e) {
			return null;
		}
	}
	
	/**
	 * 等待获取元素<br>
	 * 每秒检测一次,超过最大时间则报错
	 * @param target
	 * @param timeSecond
	 * @return
	 */
	public WebElement waitForElement(By target, int timeSecond) {  
		WebElement element = null;
        for (int second = 0;; second++) {   
            if (second >= timeSecond) {   
                logger.debug(MessageFormat.format("Element: {0} can't be found after {1} seconds", target.toString(), timeSecond));   
                throw new NoSuchElementException("元素不存在:" + target.toString());
            }   
            element = isElementPresent(target);
            if (element != null) {
            	return element;
            }             
            logger.debug("wait for element in " + second + "second！");
            sleep(1000); 
        }   
    }
	
	/**
	 * 等待获取元素,默认10s<br>
	 * 每秒检测一次,超过最大时间则报错
	 * @param target
	 * @return
	 */
	public WebElement waitForElement(By target) {
		return waitForElement(target, MAX_SECOND_TO_WAIT_ELEMENT);
	}
	
	
	
	/************************************************************************************************************************/
	/**
	 * 开启一个测试步骤
	 * @param stepName
	 * @throws Exception 
	 */
	public void beginStep(String stepName) throws Exception {
		if (currentStep != null) {
			throw new Exception("当前有未结束的测试步骤,不能开启新的测试步骤");
		}
		
		//开启步骤
		currentStep = new TestStep();
		currentStep.setStepName(stepName);
	}
	
	/**
	 * 结束一个测试步骤
	 * @param stepName 步骤名称需要之前的相同
	 * @param ifSuccess
	 * @throws Exception 
	 */
	public void endStep(boolean ifSuccess) throws Exception {
		endStep(ifSuccess, "");
	}
	
	/**
	 * 结束一个测试步骤
	 * @param stepName 步骤名称需要之前的相同
	 * @param ifSuccess
	 * @param mark 可以添加的备注信息
	 * @throws Exception 
	 */
	public void endStep(boolean ifSuccess, String mark, Object ... params) throws Exception {
		if (currentStep == null) {
			throw new Exception("当前未开启任何测试步骤");
		}
		
		currentStep.setMark(mark == null ? "" : MessageFormat.format(mark, params));
		currentStep.setStatus(ifSuccess);
		currentStep.setTestTime(new Date());
		
		steps.add(currentStep);
		currentStep = null;
	}
	
	public void setDriver(WebDriver driver) {
		this.driver = driver;
	}
	
	public WebDriver getDriver() {
		return driver;
	}
	
	public void setResultStatus(String status) {
		resultMap.put(RESULT_STATUS_ATTRIBUTE_NAME, status);
	}
	
	public void setResultMark(String mark) {
		resultMap.put(RESULT_MARK_ATTRIBUTE_NAME, mark);
	}
	
	public void setCaseName(String caseName) {
		resultMap.put(RESULT_CASE_ATTRIBUTE_NAME, caseName);

	}
	
	public void setTestData(String testData) {
		// TODO Auto-generated method stub
		resultMap.put(RESULT_TEST_DATA_ATTRIBUTE_NAME, testData);
	}
	
	@SuppressWarnings("unchecked")
	public void addScreenShot(String path) {
		List<String> list = (List<String>) resultMap.get(RESULT_SCREENSHOT_ATTRIBUTE_NAME);
		list.add(path);
		if (currentStep != null) {
			currentStep.setScreenshot(path);
		}
	}
	
	/**
	 * 调用公用模块<br>
	 * <strong>
	 * 类路径为com.dcits.business.module.xxx时则moduleName为xxx<br>
	 * 类路径为com.dcits.business.module.login.xxx时则moduleName为login.xxx
	 * </strong>
	 * @param moduleName
	 * @return 该模块执行的结果Map,参见本类
	 * @throws Exception 
	 */
	public Map<String, Object> callPublicModule(String moduleName, Map<String, String> params) throws Exception {
		logger.info("调用公用模块" +  PUBLIC_MODULE_PACKGE_PREFIX + "." + moduleName);
		BaseTest baseTest = null;
		try {
			baseTest = (BaseTest) Class.forName(PUBLIC_MODULE_PACKGE_PREFIX + "." + moduleName).newInstance();
		} catch (Exception e) {
			// TODO: handle exception
			//logger.error("公用模块" + PUBLIC_MODULE_PACKGE_PREFIX + "." + moduleName + "不存在!", e);
			throw new Exception("公用模块" + PUBLIC_MODULE_PACKGE_PREFIX + "." + moduleName + "不存在!");			
		}
		DcitsUtil.fillingTestData(baseTest, params);		
		baseTest.setDriver(driver);
		Long beginTime = System.currentTimeMillis();
		try {		
			baseTest.execute();
		} catch (Exception e) {
			// TODO: handle exception
			logger.error("公用模块" + PUBLIC_MODULE_PACKGE_PREFIX + "." + moduleName + "执行出错!", e);
			throw e;
			
		}	
		Long endTime = System.currentTimeMillis();
		baseTest.getResultMap().put(BaseTest.RESULT_FINISH_TIME_ATTRIBUTE_NAME, DcitsUtil.getFormatCurrentTime());
		baseTest.getResultMap().put(BaseTest.RESULT_USE_TIME_ATTRIBUTE_NAME, String.valueOf((endTime - beginTime) / 1000));
		
		return baseTest.getResultMap();
		
	}
	
	/**
	 * 执行sql并获取,返回多条信息只会取第一条
	 * @param dbName
	 * @param sql
	 * @return
	 * @throws Exception 
	 */
	public String execSql(String dbName, String sql) throws Exception {
		TestDB db = DcitsUtil.dbConnections.get(dbName);
		if (db == null) {
			throw new Exception("没有标识为" + dbName + "数据库信息!");
		}
		logger.info("执行sql:" + sql + ",数据库标识为" + dbName);
		return db.execSql(sql);
	}
	
	/**
	 * 执行sql并获取,返回多条信息只会取第一条
	 * @param dbName
	 * @param oper
	 * @return
	 * @throws Exception 
	 */
	public String execSql(String dbName, ExecOperater oper) throws Exception {
		TestDB db = DcitsUtil.dbConnections.get(dbName);
		if (db == null) {
			throw new Exception("没有标识为" + dbName + "数据库信息!");
		}
		logger.info("自定义执行sql,数据库标识为" + dbName);
		return db.execSql(oper);
	}
	
	public void sleep(int millisecond) {
		try {
			Thread.sleep(millisecond);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public Map<String, Object> getResultMap() {
		return resultMap;
	}
	
	public List<TestStep> getSteps() {
		return steps;
	}
	
	public void setSteps(List<TestStep> steps) {
		this.steps = steps;
	}
}
